/*******************************************************************************
Texture2D.h

	Author:		LiuDingXi
	e-mail:		liudx@gmail.com
	Date time:	2009-08-07

	Copyright (C) 2009
	All rights reserved.
	
*******************************************************************************/

#ifndef _TEXTURE2D_H_
#define _TEXTURE2D_H_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "ximage.h"

class CTexture2D
{
public:
	static inline bool IsPowerOf2(int n)
	{
		if(n<=0) return false;
		return (n & (n-1))==0;
	}

	static inline int NextPowerOf2(int v)
	{
		v--;
		v |= v >> 1;
		v |= v >> 2;
		v |= v >> 4;
		v |= v >> 8;
		v |= v >> 16;
		v++;
		return v;
	};
	static inline int PrevPowerOf2(int v)
	{
		return (NextPowerOf2(v) >> 1);
	}

public:
	CTexture2D()
	{
		m_nTexId = 0;
	}
	~CTexture2D()
	{
		Free();
	}

	GLfloat	m_nWidth;
	GLfloat m_nHeight;

	GLfloat	m_fWidth;
	GLfloat m_fHeight;

	GLuint	m_nTexId;

	BOOL Load(LPCTSTR szFile)
	{
		CxImage Image;
		if(Image.Load(szFile, CXIMAGE_FORMAT_PNG))
		{
			return Load(Image);
		}
		return FALSE;
	}
	BOOL Load(CxImage& Image)
	{
		if(!Image.AlphaIsValid())
		{
			Image.AlphaCreate();
		}

		CxImage* pImage = &Image;

		GLsizei w = Image.GetWidth();
		GLsizei h = Image.GetHeight();

		m_nWidth = (GLfloat)w;
		m_nHeight = (GLfloat)h;

		m_fWidth = 1.0f;
		m_fHeight = 1.0f;


		CxImage Image2;

		if( !IsPowerOf2(w) || !IsPowerOf2(h) )
		{
			GLsizei sw = w;
			GLsizei sh = h;

			w = NextPowerOf2(sw);
			h = NextPowerOf2(sh);

			m_fWidth = m_nWidth / (GLfloat)w;
			m_fHeight = m_nHeight / (GLfloat)h;
			
			Image2.Create(w, h, 24);

			int x = 0;
			int y = h - sh;

			Image2.SubImage(Image, x, y, true);

			pImage = &Image2;
		}

		BYTE* pPixes = NULL;
		long lSize = 0;
		
		pImage->Encode2RGBA(pPixes, lSize);
		if(pPixes)
		{
			glGenTextures(1, &m_nTexId);

			
			glBindTexture( GL_TEXTURE_2D, m_nTexId);
			glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0,
				GL_RGBA, GL_UNSIGNED_BYTE, pPixes);
			
		#if(_OS_ == _OS_WINNT_)
			glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
			glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		#else
			glTexParameterx( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
			glTexParameterx( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		#endif
			Image.FreeMemory(pPixes);
			return TRUE;
		}
		return FALSE;
	}
	virtual void Free()
	{
		if(m_nTexId)
		{
			glDeleteTextures(1, &m_nTexId);
			m_nTexId = 0;
		}
	}

	virtual void DrawAt(int x, int y, const GCLIP& rClip, const GOPER& rOper)
	{
		GLfloat x1 = (GLfloat)x;
		GLfloat y1 = (GLfloat)y;
		GLfloat x2 = x1 + rClip.cx;
		GLfloat y2 = y1 + rClip.cy;

		const GLfloat squareVertices[] = {
			x1, y1,			x2, y1,
			x1, y2,			x2, y2,
		};

		GLfloat tx1 = (GLfloat)rClip.x/(GLfloat)m_nWidth;
		GLfloat tx2 = (GLfloat)(rClip.x + rClip.cx)/(GLfloat)m_nWidth;
		GLfloat ty1 = 1.0f - (GLfloat)(rClip.y)/(GLfloat)m_nHeight;
		GLfloat ty2 = 1.0f - (GLfloat)(rClip.y + rClip.cy)/(GLfloat)m_nHeight;

		const GLfloat coordinates[] = {
			tx1, ty1,			tx2, ty1,
			tx1, ty2,			tx2, ty2,
		};
		
		glBindTexture(GL_TEXTURE_2D, m_nTexId);
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(2, GL_FLOAT, 0, squareVertices);
		glEnableClientState( GL_TEXTURE_COORD_ARRAY );
		glTexCoordPointer(2, GL_FLOAT, 0, coordinates);
		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	}

	virtual void DrawAt(int x, int y)
	{
		GLfloat x1 = (GLfloat)x;
		GLfloat y1 = (GLfloat)y;
		GLfloat x2 = x1 + (GLfloat)m_nWidth;
		GLfloat y2 = y1 + (GLfloat)m_nHeight;

		const GLfloat squareVertices[] = {
			x1, y1,			x2, y1,
			x1, y2,			x2, y2,
		};

		GLfloat tx1 = 0.0f;
		GLfloat tx2 = m_fWidth;
		GLfloat ty1 = 1.0f;
		GLfloat ty2 = 1.0f - m_fHeight;

		const GLfloat coordinates[] = {
			tx1, ty1,		tx2, ty1,
			tx1, ty2,		tx2, ty2,
		};
		
		glBindTexture(GL_TEXTURE_2D, m_nTexId);
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(2, GL_FLOAT, 0, squareVertices);
		glEnableClientState( GL_TEXTURE_COORD_ARRAY );
		glTexCoordPointer(2, GL_FLOAT, 0, coordinates);
		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	}
};

class CTexture2DEx : public CTexture2D
{
public:
	CTexture2DEx()
	{
		m_pTextures = NULL;
		m_pArray = NULL;
		m_nArray = 0;
		m_nCount = 0;
		m_fWidth = 1.0f;
		m_fHeight = 1.0f;
	}
	~CTexture2DEx()
	{
		Free();
		delete[] m_pTextures;
		m_pTextures = NULL;
		delete[] m_pArray;
		m_pArray = NULL;
		m_nArray = 0;
		m_nCount = 0;
	}

	BOOL ShotOf(int x, int y, int cx, int cy)
	{
		if(Split(x, y, cx, cy))
		{
			return TRUE;
		}
		return FALSE;
	}

	void DrawAt(int x, int y)
	{
		if(m_pArray)
		{
			int nIndex = 0;
			int px = x;

			for(int i=0; i<m_nCol; i++)
			{
				int py = y;
				int cx = m_pArray[nIndex].cx;
				for(int j=0; j<m_nRow; j++)
				{
					m_nWidth = (GLfloat)m_pArray[nIndex].cx;
					m_nHeight = (GLfloat)m_pArray[nIndex].cy;
					m_nTexId = m_pTextures[nIndex];
					nIndex++;

					CTexture2D::DrawAt(px, py);

					py += m_nHeight;
				}
				px += cx;
			}
		}
	}

	void Free()
	{
		if(m_pTextures && m_nCount > 0)
		{
			glDeleteTextures(m_nCount, m_pTextures);
			m_nCount = 0;
		}
	}


protected:

	struct NODE
	{
		int		cx;
		int		cy;
	};
	GLuint*	m_pTextures;

	NODE*	m_pArray;
	int		m_nArray;
	int		m_nCount;

	int		m_nRow;
	int		m_nCol;

	BOOL Tmake(int x, int y, int cx, int cy)
	{
		if(m_nCount < m_nArray)
		{
			m_pArray[m_nCount].cx = cx;
			m_pArray[m_nCount].cy = cy;

			GLuint nTexId;
			glGenTextures(1, &nTexId);
			glBindTexture( GL_TEXTURE_2D, nTexId);
			m_pTextures[m_nCount] = nTexId;

			glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x, y, cx, cy, 0);

		#if(_OS_ == _OS_WINNT_)
			glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
			glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		#else
			glTexParameterx( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
			glTexParameterx( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		#endif

			m_nCount++;
			return TRUE;
		}
		return FALSE;
	}

	BOOL Split(int x, int y, int cx, int cy)
	{
		m_nRow = 0;
		m_nCol = 0;

		int w = cx;

		while(w > 0)
		{
			int pw = w;
			if(!IsPowerOf2(pw))
			{
				pw = PrevPowerOf2(pw);
			}
			w -= pw;
			m_nCol++;
		}

		int h = cy;
		while(h > 0)
		{
			int ph = h;
			if(!IsPowerOf2(ph))
			{
				ph = PrevPowerOf2(ph);
			}
			h -= ph;
			m_nRow++;
		}

		if(m_nRow > 0 && m_nCol > 0)
		{
			int nCount = m_nRow * m_nCol;
			if(nCount > m_nArray)
			{
				Free();
				delete[] m_pTextures;
				delete[] m_pArray;
				m_nArray = nCount;
				m_pArray = new NODE[m_nArray];
				m_pTextures = new GLuint[m_nArray];
			}
			m_nCount = 0;

			if(m_pArray && m_pTextures)
			{
				int px = x;
				int py = y + cy;

				w = cx;
				while(w > 0)
				{
					int pw = w;
					if(!IsPowerOf2(pw))
					{
						pw = PrevPowerOf2(pw);
					}
					w -= pw;

					py = y + cy;
					h = cy;
					while(h > 0)
					{
						int ph = h;
						if(!IsPowerOf2(ph))
						{
							ph = PrevPowerOf2(ph);
						}
						h -= ph;
						
						py -= ph;
						Tmake(px, py, pw, ph);
					}
					px += pw;
				}
			}
			return TRUE;
		}
		return FALSE;
	}
};

#endif//_TEXTURE2D_H_
